# Copyright (c) 2008 Simon Menke
# 
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
# 
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

module SexyNestedResources # :nodoc:
  module ActionController # :nodoc:
    def self.included(base)
      base.extend ClassMethods
    end
    
    module ClassMethods
      
      # adds a before filter which loads all the parent resources.
      # 
      #  when: request.path          # => /projects/231/team_members/45/tasks
      #   and: parent_resource_types # => [:project, :team_member]
      #   and: params                # => { "team_member_id" => 45, "project_id" => 231, ... }
      #  then: parent_resource       # => TeamMember.find(45)
      #   and: @parent_resource      # => TeamMember.find(45)
      #   and: @team_member          # => TeamMember.find(45)
      #   and: @project              # => Project.find(231)
      #
      def preload_parent_resources(options={})
        return if self.included_modules.include?(SexyNestedResources::ActionController::FilterMethods)
        send :include, SexyNestedResources::ActionController::FilterMethods
        send :include, SexyNestedResources::ActionController::InstanceMethods
        class_eval do
          before_filter :preload_parent_resources, options
          helper_method :parent_resource_types
        end
      end
    end
    
    module InstanceMethods
      # returns the parent resource types ordered by there level in the request url.
      #  when: request.path          # => /projects/231/team_members/45/tasks
      #  then: parent_resource_types # => [:project, :team_member]
      #
      def parent_resource_types
        request.parent_resource_types
      end
      
      # returns the the last parent resource.
      # 
      #  when: request.path          # => /projects/231/team_members/45/tasks
      #   and: parent_resource_types # => [:project, :team_member]
      #   and: params                # => { "team_member_id" => 45, "project_id" => 231, ... }
      #  then: parent_resource       # => TeamMember.find(45)
      #
      def parent_resource
        @parent_resource
      end
    end
  
    module FilterMethods # :nodoc:
    protected
      def preload_parent_resources
        resource = nil
        parent_resource_types.each do |resource_name|
          resource_class = resource_name.classify.constantize
          begin
            resource = resource_class.find(params["#{resource_name}_id"])
          rescue ActiveRecord::RecordNotFound => e
            resource = nil
          end
          set_parent_resource(resource_class, resource)
        end
        resource ||= ::SexyNestedResources::ParentResourceProxy.new
        instance_variable_set "@parent_resource", resource
      end
      
      def set_parent_resource(resource_class, resource)
        instance_variable_set "@#{resource_class.to_s.underscore}", resource
        set_parent_resource(resource_class.superclass, resource) unless resource_class.superclass == ActiveRecord::Base
      end
    end

  end
  
  class ParentResourceProxy # :nodoc:
    def method_missing(m, *args)
      ParentResourceAssociationProxy.new(m.to_s.classify.constantize)
    end
  end
  
  class ParentResourceAssociationProxy # :nodoc:
    attr_accessor :model_class
    def initialize(model_class)
      self.model_class = model_class
    end
    def build(*args)
      self.model_class.new(*args)
    end
    def method_missing(m, *args)
      self.model_class.send(m, *args)
    end
  end
end

